// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cast/sender/size_adaptable_video_encoder_base.h"

#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "media/base/video_frame.h"

namespace media {
namespace cast {

    SizeAdaptableVideoEncoderBase::SizeAdaptableVideoEncoderBase(
        const scoped_refptr<CastEnvironment>& cast_environment,
        const FrameSenderConfig& video_config,
        const StatusChangeCallback& status_change_cb)
        : cast_environment_(cast_environment)
        , video_config_(video_config)
        , status_change_cb_(status_change_cb)
        , frames_in_encoder_(0)
        , next_frame_id_(FrameId::first())
        , weak_factory_(this)
    {
        cast_environment_->PostTask(
            CastEnvironment::MAIN,
            FROM_HERE,
            base::Bind(status_change_cb_, STATUS_INITIALIZED));
    }

    SizeAdaptableVideoEncoderBase::~SizeAdaptableVideoEncoderBase()
    {
        DestroyEncoder();
    }

    bool SizeAdaptableVideoEncoderBase::EncodeVideoFrame(
        const scoped_refptr<media::VideoFrame>& video_frame,
        const base::TimeTicks& reference_time,
        const FrameEncodedCallback& frame_encoded_callback)
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));

        const gfx::Size frame_size = video_frame->visible_rect().size();
        if (frame_size.IsEmpty()) {
            DVLOG(1) << "Rejecting empty video frame.";
            return false;
        }
        if (frames_in_encoder_ == kEncoderIsInitializing) {
            VLOG(1) << "Dropping frame since encoder initialization is in-progress.";
            return false;
        }
        if (frame_size != frame_size_ || !encoder_) {
            VLOG(1) << "Dropping this frame, and future frames until a replacement "
                       "encoder is spun-up to handle size "
                    << frame_size.ToString();
            TrySpawningReplacementEncoder(frame_size);
            return false;
        }

        const bool is_frame_accepted = encoder_->EncodeVideoFrame(
            video_frame,
            reference_time,
            base::Bind(&SizeAdaptableVideoEncoderBase::OnEncodedVideoFrame,
                weak_factory_.GetWeakPtr(),
                frame_encoded_callback));
        if (is_frame_accepted)
            ++frames_in_encoder_;
        return is_frame_accepted;
    }

    void SizeAdaptableVideoEncoderBase::SetBitRate(int new_bit_rate)
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        video_config_.start_bitrate = new_bit_rate;
        if (encoder_)
            encoder_->SetBitRate(new_bit_rate);
    }

    void SizeAdaptableVideoEncoderBase::GenerateKeyFrame()
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        if (encoder_)
            encoder_->GenerateKeyFrame();
    }

    std::unique_ptr<VideoFrameFactory>
    SizeAdaptableVideoEncoderBase::CreateVideoFrameFactory()
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        return nullptr;
    }

    void SizeAdaptableVideoEncoderBase::EmitFrames()
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        if (encoder_)
            encoder_->EmitFrames();
    }

    StatusChangeCallback
    SizeAdaptableVideoEncoderBase::CreateEncoderStatusChangeCallback()
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        return base::Bind(&SizeAdaptableVideoEncoderBase::OnEncoderStatusChange,
            weak_factory_.GetWeakPtr());
    }

    void SizeAdaptableVideoEncoderBase::OnEncoderReplaced(
        VideoEncoder* replacement_encoder) { }

    void SizeAdaptableVideoEncoderBase::DestroyEncoder()
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        // The weak pointers are invalidated to prevent future calls back to |this|.
        // This effectively cancels any of |encoder_|'s posted tasks that have not yet
        // run.
        weak_factory_.InvalidateWeakPtrs();
        encoder_.reset();
    }

    void SizeAdaptableVideoEncoderBase::TrySpawningReplacementEncoder(
        const gfx::Size& size_needed)
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));

        // If prior frames are still encoding in the current encoder, let them finish
        // first.
        if (frames_in_encoder_ > 0) {
            encoder_->EmitFrames();
            // Check again, since EmitFrames() is a synchronous operation for some
            // encoders.
            if (frames_in_encoder_ > 0)
                return;
        }

        if (frames_in_encoder_ == kEncoderIsInitializing)
            return; // Already spawned.

        DestroyEncoder();
        frames_in_encoder_ = kEncoderIsInitializing;
        OnEncoderStatusChange(STATUS_CODEC_REINIT_PENDING);
        VLOG(1) << "Creating replacement video encoder (for frame size change from "
                << frame_size_.ToString() << " to "
                << size_needed.ToString() << ").";
        frame_size_ = size_needed;
        encoder_ = CreateEncoder();
        DCHECK(encoder_);
    }

    void SizeAdaptableVideoEncoderBase::OnEncoderStatusChange(
        OperationalStatus status)
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        if (frames_in_encoder_ == kEncoderIsInitializing && status == STATUS_INITIALIZED) {
            // Begin using the replacement encoder.
            frames_in_encoder_ = 0;
            OnEncoderReplaced(encoder_.get());
        }
        status_change_cb_.Run(status);
    }

    void SizeAdaptableVideoEncoderBase::OnEncodedVideoFrame(
        const FrameEncodedCallback& frame_encoded_callback,
        std::unique_ptr<SenderEncodedFrame> encoded_frame)
    {
        DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
        --frames_in_encoder_;
        DCHECK_GE(frames_in_encoder_, 0);

        if (encoded_frame)
            next_frame_id_ = encoded_frame->frame_id + 1;
        frame_encoded_callback.Run(std::move(encoded_frame));
    }

} // namespace cast
} // namespace media
